If you write code, write tests. – The Way of Testivus
目录 Table of Contents
背景
单元测试的重要性无需多言,但是由于业务迭代得超快,就很难再分出时间和精力投入到质量保障中去。尽管如此,等待接口联调的过程中既漫长无比又压力山大(毕竟谁想被挂 Tapd Bug 单呢 _(:з」∠)_
在之前的 Unit Test Pre-study and Practice in Python 文章中,已经简要地介绍了测试流程和测试用例的核心理念,本文将沿用这些方法论以及结合各位大神的博客和日常工作的体验,着重讨论以下内容:
- 技术选型:
Golang
作为一门静态和强类型语言,相比于Python
的动态和弱类型特性,带来了一些新的挑战。- 工程实践:业务逻辑快速验证的最佳实践,暂时不涉及脚手架设计。
技术选型
单测框架
testing
官方原生库,无断言机制,编写较繁琐。
编写
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16func DoSomething() error {
// ...
}
func TestDoSomething(t *testing.T) {
// Arrange
// ...
// Act
err := DoSomething()
// Assert
if err != nil {
t.Errorf(err.Error())
}
}运行
1
go test -v
GoConvey
兼容官方原生库和模拟框架,有断言机制,编写较简洁。
编写
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18func DoSomething() error {
// ...
}
func TestDoSomething(t *testing.T) {
Convey("TestDoSomething", t, func() {
Convey("Condition 1", func() {
// Arrange
// ...
// Act
err := DoSomething()
// Assert
So(err, ShouldBeNil)
})
})
}运行
1
2
3
4# Cmd
go test -v
# Web
$GOPATH/bin/goconvey
模拟框架
GoMonkey
Mock 全局变量 & 函数方法
全局变量
1
2
3
4
5patches := ApplyFuncVar(&funcVar, mockVar)
defer patches.Reset()
patches := ApplyGlobalVar(&funcVar, mockVar)
defer patches.Reset()函数方法
1
2
3
4
5
6patches := ApplyFunc(funcName, mockFunc)
defer patches.Reset()
var ptr *SomeClass
patches := ApplyMethod(reflect.TypeOf(ptr), "methodName", mockMethod)
defer patches.Reset()
GoMock
Mock 接口
生成
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55// repository.go
// Repository is the interface to be mocked
type Repository interface {
Create(val interface{}) error
Get(key string) (interface{}, error)
Update(key string, val interface{}) error
Delete(key string) error
}
// mockgen -source=${file}
// mockgen ${package} ${interface1}, ... , ${interfaceN}
// mock_repository.go
// MockRepository is a mock of Repository interface
type MockRepository struct {
ctrl *gomock.Controller
recorder *MockRepositoryMockRecorder
}
// MockRepositoryMockRecorder is the mock recorder for MockRepository
type MockRepositoryMockRecorder struct {
mock *MockRepository
}
// NewMockRepository creates a new mock instance
func NewMockRepository(ctrl *gomock.Controller) *MockRepository {
mock := &MockRepository{ctrl: ctrl}
mock.recorder = &MockRepositoryMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (_m *MockRepository) EXPECT() *MockRepositoryMockRecorder {
return _m.recorder
}
// Create mocks base method
func (_m *MockRepository) Create(_param0 []byte) error {
// ...
}
// Get mocks base method
func (_m *MockRepository) Get(_param0 string) (interface{}, error) {
// ...
}
// Update mocks base method
func (_m *MockRepository) Update(_param0 string, _param1 interface{}) error {
// ...
}
// Delete mocks base method
func (_m *MockRepository) Delete(_param0 string) error {
// ...
}使用
1
2
3
4
5
6
7
8
9
10ctrl := gomock.NewController(t)
defer ctrl.Finish()
mockRepo := mock_repo.NewMockRepository(ctrl)
InOrder (
createCall := mockRepo.EXPECT().Create(mockVal).Return(mockErr)
getCall := mockRepo.EXPECT().Get(mockKey).Return(mockVal, mockErr).Times(7)
updateCall := mockRepo.EXPECT().Update(mockKey, mockVal).Return(mockErr)
deleteCall := mockRepo.EXPECT().Create(mockKey).Return(mockErr)
)
sqlmock
Mock 数据库
1 | // Arrange |
httptest
Mock 服务器
1 | // Arrange |
工程实践
- to be continued…
总结
单测框架用于断言:使用
GoConvey
框架。模拟框架用于替换:对于全局变量和函数方法使用
GoMonkey
框架,对于接口使用GoMock
框架,对于数据库使用sqlmock
框架,对于服务器使用httptest
框架。
参考链接
以下文章对本文亦有贡献 :)
附录
使用文档和最佳实践 :)